local skynet = require "skynet"
local s = require "service"
local CMD = require "game_cmd"
local common = require "scene_common"
local aoi = require "aoi_mgr"
local map = require "map"

local balls = {} -- [playerid] = ball
local foods = {} -- [id] = food
local food_maxid = 0
local food_count = 0

local map_length = 20
local map_width = 20

-- 球
function ball()
    local m = {
        playerid = nil,
        node = nil,
        agent = nil,
        x = math.random(0, map_length),
        y = math.random(0, map_width),
        size = 2,
        speedx = 10,
        speedy = 10,
        moving = false,
        direction = common.Direction.UP,
        aoi_obj = nil,
    }
    return m
end

-- 食物
function food()
    local m = {
        id = nil,
        x = math.random(0, map_length),
        y = math.random(0, map_width)
    }
    return m
end

-- 球列表
local function balllist_msg()
    local msg = {"balllist"}
    for i, v in pairs(balls) do
        table.insert(msg, v.playerid)
        table.insert(msg, v.x)
        table.insert(msg, v.y)
        table.insert(msg, v.size)
    end
    return msg
end

-- 食物列表
local function foodlist_msg()
    local msg = {"foodlist"}
    for i, v in pairs(foods) do
        table.insert(msg, v.id)
        table.insert(msg, v.x)
        table.insert(msg, v.y)
    end
    return msg
end

-- 广播
function broadcast(msg, cmd_id)
    for i, v in pairs(balls) do
        s.send(v.node, v.agent, "send", msg, cmd_id)
    end
end

function broadcast_aoi(x, y, msg, cmd_id)
    local result = aoi.query_range(x, y)
    skynet.error("broadcast_aoi", #result)
    for _, aoi_obj in pairs(result) do
        local player_id = aoi_obj.player_id
        local b = balls[player_id]
        s.send(b.node, b.agent, "send", msg, cmd_id)
    end
end

-- 进入
s.resp.enter = function(source, playerid, node, agent)
    if balls[playerid] then
        return false
    end
    local b = ball()
    b.playerid = playerid
    b.node = node
    b.agent = agent
    b.aoi_obj = aoi.create_aoi_obj(playerid, b.x, b.y)
    map.add_player_obj(playerid, b.x, b.y)
    map.test_print_map()

    -- 广播
    local broadcast_msg = {
        status = 1,
        msg = playerid .. "进入场景" .. s.id,
        type_id = s.id,
        unique_id = s.id,
        players = {{
            x = b.x,
            y = b.y;
            z = 0;
            speed = b.speedx;
            direction = b.direction;
            id = b.playerid;
        }},
    }
    broadcast(broadcast_msg, CMD.SERVER_ENTER)
    -- 记录
    balls[playerid] = b

    local result = aoi.query_range(b.x, b.y)
    local players = {}
    skynet.error("enter scene aoi:", #result)
    for _, aoi_obj in pairs(result) do
        table.insert(players, {
            x = aoi_obj.x,
            y = aoi_obj.y;
            z = 0;
            speed = balls[aoi_obj.player_id].speedx;
            direction = balls[aoi_obj.player_id].direction;
            id = aoi_obj.player_id;
        })
    end
    -- 回应
    local msg_ret = {
        status = 1,
        msg = "您已进入场景" .. s.id .. " 坐标：" .. b.x .. "," .. b.y .. " 速度:" .. b.speedx,
        type_id = s.id,
        unique_id = s.id,
        players = players,
    }

    s.send(b.node, b.agent, "send", msg_ret, CMD.SERVER_ENTER)
    -- 发战场信息
    -- s.send(b.node, b.agent, "send", balllist_msg())
    -- s.send(b.node, b.agent, "send", foodlist_msg())
    return true
end

-- 退出
s.resp.leave = function(source, playerid)
    local ball = balls[playerid]
    if not ball then
        return false
    end

    ball.moving = false
    local agent = ball.agent
    local node = ball.node
    balls[playerid] = nil
    aoi.remove_aoi_obj(ball.aoi_obj)
    map.remove_player_obj(playerid)
    ball.aoi_obj = nil

    local leavemsg = {"leave", playerid}

    local leave_scene_msg = {
        status = 1,
        msg = playerid .. "退出场景" .. s.id,
        player_id = playerid,
    }

    broadcast(leave_scene_msg, CMD.SERVER_LEAVE)

    s.send(node, agent, "send", leave_scene_msg, CMD.SERVER_LEAVE)
    skynet.error("leave scene", playerid, skynet.self())
end

-- 改变速度
s.resp.shift = function(source, playerid, x, y)
    local b = balls[playerid]
    if not b then
        return false
    end
    b.speedx = x
    b.speedy = y
end

s.resp.move = function(source, player_id, direction)
    local b = balls[player_id]
    if not b then
        return false
    end

    b.moving = true
    b.direction = direction
end

s.resp.stop_move = function(source, player_id)
    local b = balls[player_id]
    if not b then
        return false
    end

    b.moving = false
end

function food_update()
    if food_count > 50 then
        return
    end

    if math.random(1, 100) < 98 then
        return
    end

    food_maxid = food_maxid + 1
    food_count = food_count + 1
    local f = food()
    f.id = food_maxid
    foods[f.id] = f

    local msg = {"addfood", f.id, f.x, f.y}
    broadcast(msg)
end

function move_update()
    for i, b in pairs(balls) do
        if b.moving then
            if b.direction == common.Direction.UP then
                b.y = b.y - b.speedy * 0.1
            elseif b.direction == common.Direction.DOWN then
                b.y = b.y + b.speedy * 0.1
            elseif b.direction == common.Direction.LEFT then
                b.x = b.x - b.speedx * 0.1
            elseif b.direction == common.Direction.RIGHT then
                b.x = b.x + b.speedx * 0.1
            end

            if b.x > map_length then
                b.x = map_length
                s.resp.stop_move(_, b.playerid)
            end
            if b.y > map_width then
                b.y = map_width
                s.resp.stop_move(_, b.playerid)
            end

            if b.x < 0 then
                b.x = 0
                s.resp.stop_move(_, b.playerid)
            end
            if b.y < 0 then
                b.y = 0
                s.resp.stop_move(_, b.playerid)
            end

            local move_msg = {
                player_info = {
                    x = b.x,
                    y = b.y;
                    z = 0;
                    speed = b.speedx;
                    direction = b.direction;
                    id = b.playerid;
                }
            }
            map.update_player_obj(b.playerid, b.x, b.y)
            map.test_print_map()
            aoi.update_aoi_obj(b.aoi_obj, b.x, b.y)
            broadcast_aoi(b.x, b.y, move_msg, CMD.SERVER_MOVE)
        end
    end
end

function eat_update()
    for pid, b in pairs(balls) do
        for fid, f in pairs(foods) do
            if (b.x - f.x) ^ 2 + (b.y - f.y) ^ 2 < b.size ^ 2 then
                b.size = b.size + 1
                food_count = food_count - 1
                local msg = {"eat", b.playerid, fid, b.size}
                broadcast(msg)
                foods[fid] = nil -- warm
            end
        end
    end
end

function update(frame)
    -- food_update()
    move_update()
    -- eat_update()
    -- 碰撞略
    -- 分裂略
end

s.init = function()
    map.init()
    map.test_print_map()

    skynet.fork(function()
        -- 保持帧率执行
        local stime = skynet.now()
        local frame = 0
        while true do
            frame = frame + 1
            local isok, err = pcall(update, frame)
            if not isok then
                skynet.error(err)
            end
            local etime = skynet.now()
            local waittime = frame * 20 - (etime - stime)
            if waittime <= 0 then
                waittime = 2
            end
            skynet.sleep(waittime)
        end
    end)
end

s.start(...)
